#include "timespan.h"

#include <util/string_util.h>

namespace cppc {

TimeSpan::TimeSpan(double value, Period period, unsigned int precision, bool round) {
    m_value = value;
    m_period = period;
    m_precision = precision;
    m_round = round;
    update();
}

TimeSpan::TimeSpan(const TimeSpan &other) {
    m_value = other.m_value;
    m_period = other.m_period;
    m_precision = other.m_precision;
    m_round = other.m_round;
}

TimeSpan::TimeSpan(const std::string &timeStr, unsigned int precision, bool round) {
    std::string valueStr = timeStr;
    cppc::trim(valueStr);
    cppc::toUpperStr(valueStr);
    double timeMSValue = std::atof(valueStr.c_str());
    if (timeMSValue > 0) {
        std::string::size_type const len = valueStr.length();
        if ((len > 3) && (valueStr.compare(len - 3, 3, "MIN") == 0)) {
            timeMSValue *= FACTOR_MIN_TO_MILLI;
        } else if ((len > 1) && (valueStr.compare(len - 1, 1, "Y") == 0)) {
            timeMSValue *= FACTOR_YEAR_TO_MILLI;
        } else if ((len > 1) && (valueStr.compare(len - 1, 1, "M") == 0)) {
            timeMSValue *= FACTOR_MONTH_TO_MILLI;
        } else if ((len > 1) && (valueStr.compare(len - 1, 1, "D") == 0)) {
            timeMSValue *= FACTOR_DAY_TO_MILLI;
            m_period = Day;
        } else if ((len > 1) && (valueStr.compare(len - 1, 1, "W") == 0)) {
            timeMSValue *= FACTOR_WEEK_TO_MILLI;
        } else if ((len > 1) && (valueStr.compare(len - 1, 1, "H") == 0)) {
            timeMSValue *= FACTOR_HOUR_TO_MILLI;
        } else if ((len > 1) && (valueStr.compare(len - 1, 1, "S") == 0)) {
            timeMSValue *= FACTOR_SEC_TO_MILLI;
        }
    }
    m_period = MilliSecond;
    m_value = timeMSValue;
    m_precision = precision;
    m_round = round;
    update();
}

TimeSpan::~TimeSpan() {
}

std::string TimeSpan::toString() const {
    std::ostringstream t_out;
    t_out << std::fixed << std::setprecision(m_precision) << m_value;

    switch (m_period) {
    case TimeSpan::MilliSecond:
        break;
    case TimeSpan::Second:
        t_out << "S";
        break;
    case TimeSpan::Minute:
        t_out << "MIN";
        break;
    case TimeSpan::Hour:
        t_out << "H";
        break;
    case TimeSpan::Day:
        t_out << "D";
        break;
    case TimeSpan::Week:
        t_out << "W";
        break;
    case TimeSpan::Month:
        t_out << "M";
        break;
    default:
        break;
    }
    return t_out.str();
}

TimeSpan TimeSpan::clone() const {
    return TimeSpan(*this);
}

TimeSpan &TimeSpan::set(double value, Period period, unsigned int precision, bool round) {
    m_period = period;
    m_value = value;
    m_precision = precision;
    m_round = round;
    update();
    return *this;
}

TimeSpan &TimeSpan::setValue(double value) {
    m_value = value;
    update();
    return *this;
}

TimeSpan &TimeSpan::setPrecision(unsigned int precision) {
    m_precision = precision;
    update();
    return *this;
}

TimeSpan &TimeSpan::setRound(bool round) {
    m_round = round;
    update();
    return *this;
}

TimeSpan &TimeSpan::setPeriod(Period period) {
    m_period = period;
    return *this;
}

TimeSpan &TimeSpan::rase() {
    switch (m_period) {
    case TimeSpan::MilliSecond:
        m_period = TimeSpan::Second;
        m_value /= FACTOR_SEC_TO_MILLI;
        break;
    case TimeSpan::Second:
        m_period = TimeSpan::Minute;
        m_value /= 60;
        break;
    case TimeSpan::Minute:
        m_period = TimeSpan::Hour;
        m_value /= 60;
        break;
    case TimeSpan::Hour:
        m_period = TimeSpan::Day;
        m_value /= 24;
        break;
    case TimeSpan::Day:
        m_period = TimeSpan::Week;
        m_value /= 7;
        break;
    case TimeSpan::Week:
        m_period = TimeSpan::Month;
        m_value /= 4;
        break;
    case TimeSpan::Month:
        m_period = TimeSpan::Year;
        m_value /= 12;
        break;
    default:
        break;
    }
    update();
    return *this;
}

TimeSpan &TimeSpan::down() {
    switch (m_period) {
    case TimeSpan::MilliSecond:
        break;
    case TimeSpan::Second:
        m_period = TimeSpan::MilliSecond;
        m_value *= FACTOR_SEC_TO_MILLI;
        break;
    case TimeSpan::Minute:
        m_period = TimeSpan::Second;
        m_value *= 60;
        break;
    case TimeSpan::Hour:
        m_period = TimeSpan::Minute;
        m_value *= 60;
        break;
    case TimeSpan::Day:
        m_period = TimeSpan::Hour;
        m_value *= 24;
        break;
    case TimeSpan::Week:
        m_period = TimeSpan::Day;
        m_value *= 7;
        break;
    case TimeSpan::Month:
        m_period = TimeSpan::Week;
        m_value *= 4;
        break;
    case TimeSpan::Year:
        m_period = TimeSpan::Month;
        m_value *= 12;
        break;
    default:
        break;
    }
    update();
    return *this;
}

TimeSpan &TimeSpan::as(Period period) {
    if (m_period < period) {
        for (; m_period < period;) {
            rase();
        }
    } else if (m_period > period) {
        for (; m_period > period;) {
            down();
        }
    }
    return *this;
}

double TimeSpan::valueAs(Period period) const {
    return clone().as(period).value();
}

TimeSpan TimeSpan::operator+(const TimeSpan &other) {
    return TimeSpan(m_value + other.valueAs(m_period), m_period);
}

TimeSpan TimeSpan::operator+(double value) {
    return TimeSpan(m_value + value, m_period);
}

TimeSpan TimeSpan::operator-(const TimeSpan &other) {
    return TimeSpan(m_value - other.valueAs(m_period), m_period);
}

TimeSpan TimeSpan::operator-(double value) {
    return TimeSpan(m_value - value, m_period);
}

TimeSpan &TimeSpan::operator+=(const TimeSpan &other) {
    m_value += other.valueAs(m_period);
    update();
    return *this;
}

TimeSpan &TimeSpan::operator+=(double value) {
    m_value += value;
    update();
    return *this;
}

TimeSpan &TimeSpan::operator-=(const TimeSpan &other) {
    m_value -= other.valueAs(m_period);
    update();
    return *this;
}

TimeSpan &TimeSpan::operator-=(double value) {
    m_value -= value;
    update();
    return *this;
}

bool TimeSpan::operator>(const TimeSpan &other) {
    return (m_period == other.m_period) ? (m_value > other.m_value) : (m_period > other.m_period);
}

bool TimeSpan::operator>=(const TimeSpan &other) {
    return (m_period == other.m_period) ? (m_value >= other.m_value) : (m_period > other.m_period);
}

bool TimeSpan::operator==(const TimeSpan &other) {
    return (m_period == other.m_period) && (m_value == other.m_value);
}

bool TimeSpan::operator!=(const TimeSpan &other) {
    return (m_period != other.m_period) && (m_value != other.m_value);
}

bool TimeSpan::operator<(const TimeSpan &other) {
    return (m_period == other.m_period) ? (m_value < other.m_value) : (m_period < other.m_period);
}

bool TimeSpan::operator<=(const TimeSpan &other) {
    return (m_period == other.m_period) ? (m_value <= other.m_value) : (m_period < other.m_period);
}

void TimeSpan::update() {
    m_value = cppc::setPrecision(m_value, m_precision, m_round);
}
} // namespace cppc
